package edu.ucsb.cs.cs185.supercam;

import java.io.IOException;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.hardware.Camera;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

public class Preview extends SurfaceView implements SurfaceHolder.Callback, Camera.PreviewCallback {

	private SurfaceHolder mHolder;
    private Camera mCamera;
    private int _h,_w;
    private int _x,_y;
    private Paint paint;
    private int[][][] _rgb;
    private int[][][] srgb;
    private int[] avg;
    private GestureDetector gd;
    
    //flags
    public boolean f_cavg;
    public boolean f_help;
    public int f_mode;
    
    // Bounding Box
    private int bb_h = 50, bb_w = 50;
    private int top = 0, left = 0;
    
    public Preview(Context context, Camera camera) {
        super(context);
        
        _rgb = null;
        avg = new int[3];
        //srgb = new int[50][50][3];
        this.gd = new GestureDetector(this.getContext(), new CustomGestureDetector());
        
        //initialize flags
        f_cavg = true;
        f_help = false;
        f_mode = 0;
        
        // Crashes if it goes to sleep, so we'll keep the screen on
        setKeepScreenOn(true);
        
        paint = new Paint();
        paint.setARGB(255, 255, 255, 255);
        paint.setTextSize(60);
        
        mCamera = camera;
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        //mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
        setWillNotDraw(false);
        
        this.setOnTouchListener(new OnTouchListener(){
			@Override
			public boolean onTouch(View v, MotionEvent event) {
			    gd.onTouchEvent(event);
			    return true;
			}
		});
        
    }
    
    public class CustomGestureDetector extends SimpleOnGestureListener {
		
		@Override
	    public boolean onDown(MotionEvent event) { 
			_x = (int)event.getX();
			_y = (int)event.getY();
			bb_w = 0;
			bb_h = 0;
	        return true;
	    }
		
		@Override
		public boolean onSingleTapConfirmed(MotionEvent event) {
			_x = (int)event.getX() - 25;
			_y = (int)event.getY() - 25;
			bb_h = 50;
			bb_w = 50;
			Preview.this.invalidate();
			return true;
		}
		
		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) {
			bb_w -= dx;
			bb_h -= dy;
			//srgb = new int[Math.abs(bb_h)][Math.abs(bb_w)][3];
			//srgb = new int[50][50][3];
			Preview.this.invalidate();
			return true;
		}
	}
    
    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d("CameraView", "Error setting camera preview: " + e.getMessage());
        }
     }
    
    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        if (mCamera != null) {
            /*
              Call stopPreview() to stop updating the preview surface.
            */
            mCamera.stopPreview();
        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    	
    	_h = h;
    	_w = w;
    	_x = w/2-25;
    	_y = h/2-25;
    	
    	//_rgb = new int[_h][_w][3];
    	srgb = new int[_h][_w][3];
    	
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null){
             // preview surface does not exist
             return;
        }
        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }
        // set preview size and make any resize, rotate or
        // reformatting changes here
        // start preview with new settings
        try {
        	
        	Camera.Parameters p = mCamera.getParameters();
        	p.setPreviewSize(w, h);
        	/*
        	p.setPreviewFormat(ImageFormat.RGB_565);
        	System.out.println(p.getSupportedPreviewFormats());
        	*/
        	mCamera.setParameters(p);
        	
            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(this);
            mCamera.startPreview();
       } catch (Exception e){
           Log.d("CameraView", "Error starting camera preview: " + e.getMessage());
       }
    }
    public void onPause() {
    	mCamera.setPreviewCallback(null);
        mCamera.release();
        mCamera = null;
    }
    
    public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }
        
        stopPreviewAndFreeCamera();
        
        mCamera = camera;
        
        if (mCamera != null) {
            requestLayout();
          
            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }
          
            /*
              Important: Call startPreview() to start updating the preview surface. Preview must 
              be started before you can take a picture.
              */
            mCamera.startPreview();
        }
    }
    
    private void stopPreviewAndFreeCamera() {

        if (mCamera != null) {
            /*
              Call stopPreview() to stop updating the preview surface.
            */
            mCamera.stopPreview();
        
            /*
              Important: Call release() to release the camera for use by other applications. 
              Applications should release the camera immediately in onPause() (and re-open() it in
              onResume()).
            */
            mCamera.release();
        
            mCamera = null;
        }
    }
    
    public void onPreviewFrame(byte[] data, Camera camera) {
    	//17 = NV21
    	/*
    	if(camera.getParameters().getPreviewFormat() == ImageFormat.RGB_565){
    		System.out.println("RGB565");
    	}
    	else{
    		System.out.println("not RGB");
    	}
    	*/
    	/*
    	if(camera.getParameters().getPreviewFormat() == ImageFormat.NV21){
    		Runnable r = new YUVThread(data);
    		new Thread(r).start();
    	}
    	*/
    	convert(srgb, data, _w, _h);
    	avg[0] = 0;
    	avg[1] = 0;
    	avg[2] = 0;
    	cavg(srgb,Math.abs(bb_h),Math.abs(bb_w));
    	//cavg(srgb);
    	this.invalidate();
    }
    
    // Helper thread class
    public class YUVThread implements Runnable {
    	private byte[] data;
    	private int[][][] temp_rgb;
    	
    	public YUVThread(byte[] data){
    		this.data = data;
    		temp_rgb = new int[_h][_w][3];
    	}
    	
    	public void run(){
    		/*
    		System.out.println("Thread run");
        	YUV_NV21_TO_RGB(temp_rgb, data, _w, _h);
        	synchronized(_rgb){
        		_rgb = temp_rgb;
        	}
        	System.out.println("Thread end");
        	*/
    		convert(temp_rgb, data, _w, _h);
    		synchronized(srgb){
    			srgb = temp_rgb;
    		}
    	}
    }
    
    @Override
    protected void onDraw(Canvas canvas){
    	super.onDraw(canvas);
    	//if(_rgb!=null){
    		//cavg(_rgb,_w,_h);
    	//}
    	
		// A Simple Text Render to test the display
        //canvas.drawText("Avg = " + avg[0] + ", " + avg[1] + ", " + avg[2], 0, _h, paint);
    	//canvas.drawText("Avg = " + _rgb[0][0][0], 0, _h, paint);
    	//System.out.println(_rgb[0][0][0]);
    	
    	// Bounding Box  	
    	drawBoundingBox(canvas);
    	
    	// Draw cavg
    	if(f_cavg){
    		draw_cavg(canvas);
    	}
    	if(f_help){
    		//Turquoise
    		paint.setARGB(255, 64, 224, 208);
    		float s = paint.getTextSize();
    		paint.setTextSize(_h/10);
    		if(f_cavg){
	    		canvas.drawText("Avg Color", 25, paint.getTextSize(), paint);
	    		canvas.drawText("RGB Value", 25, paint.getTextSize()*9, paint);
    		}
    		paint.setTextAlign(Paint.Align.RIGHT);
        	canvas.drawText("-Tap to select", _w, paint.getTextSize(), paint);
        	canvas.drawText("-Drag for box select", _w, paint.getTextSize()*2, paint);
        	paint.setTextAlign(Paint.Align.LEFT);
        	paint.setTextSize(s);
    	}
	}
	
    private void drawBoundingBox(Canvas canvas){
    	// paint for bounding box, defaults green outline
    	paint.setARGB(255, 0, 255, 0);
    	paint.setStyle(Paint.Style.STROKE);
    	
    	int yf = _y + bb_h;
    	int xf = _x + bb_w;
    	
    	if(bb_w < 0){
    		if(bb_h < 0){
    			canvas.drawRect(xf, yf, _x, _y, paint);
    			left = xf;
    			top = yf;
    		} else {
    			canvas.drawRect(xf, _y, _x, yf, paint);
    			left = xf;
    			top = _y;
    		}
    	} else {
    		if(bb_h < 0){
    			canvas.drawRect(_x, yf, xf, _y, paint);
    			left = _x;
    			top = yf;
    		} else {
    			canvas.drawRect(_x, _y, xf, yf, paint);
    			left = _x;
    			top = _y;
    		}
    	}
    	//canvas.drawRect(_x, _y, xf, yf, paint);
    }
    
    public static void YUV_NV21_TO_RGB(int[][][] rgb, byte[] yuv, int width, int height) {
        final int frameSize = width * height;

        final int ii = 0;
        final int ij = 0;
        final int di = +1;
        final int dj = +1;
        
        // temporary rgb array
        // I'm worried about this overworking the garbage collection, or even just memory.
        
        //System.out.println("func");
        
        //int a = 0;
        for (int i = 0, ci = ii; i < height; ++i, ci += di) {
            for (int j = 0, cj = ij; j < width; ++j, cj += dj) {
                int y = (0xff & ((int) yuv[ci * width + cj]));
                int v = (0xff & ((int) yuv[frameSize + (ci >> 1) * width + (cj & ~1) + 0]));
                int u = (0xff & ((int) yuv[frameSize + (ci >> 1) * width + (cj & ~1) + 1]));
                y = y < 16 ? 16 : y;

                int r = (int) (1.164f * (y - 16) + 1.596f * (v - 128));
                int g = (int) (1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
                int b = (int) (1.164f * (y - 16) + 2.018f * (u - 128));

                r = r < 0 ? 0 : (r > 255 ? 255 : r);
                g = g < 0 ? 0 : (g > 255 ? 255 : g);
                b = b < 0 ? 0 : (b > 255 ? 255 : b);

                //argb[a++] = 0xff000000 | (r << 16) | (g << 8) | b;
                rgb[i][j][0] = r;
                rgb[i][j][1] = g;
                rgb[i][j][2] = b;
            }
        }
        //System.out.println("stuff");
        //synchronized(lock){
        	//rgb = temp_rgb;
        	//System.out.println("stuff");
        //}
    }
    
    public void convert(int[][][] rgb, byte[] yuv, int width, int height) {
        final int frameSize = width * height;

        final int ii = 0;
        final int ij = 0;
        final int di = +1;
        final int dj = +1;
        
        for (int i = top, ci = ii+(top), ty = 0; i < top+Math.abs(bb_h); ++i, ci += di, ty++) {
            for (int j = left, cj = ij+(left), tx = 0; j < left+Math.abs(bb_w); ++j, cj += dj, tx++) {
            	if(i >= height | j >= width | i < 0 | j < 0){
            		rgb[ty][tx][0] = -1;
	                rgb[ty][tx][1] = -1;
	                rgb[ty][tx][2] = -1;
            	}
            	else {
	                int y = (0xff & ((int) yuv[ci * width + cj]));
	                int v = (0xff & ((int) yuv[frameSize + (ci >> 1) * width + (cj & ~1) + 0]));
	                int u = (0xff & ((int) yuv[frameSize + (ci >> 1) * width + (cj & ~1) + 1]));
	                y = y < 16 ? 16 : y;
	
	                int r = (int) (1.164f * (y - 16) + 1.596f * (v - 128));
	                int g = (int) (1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
	                int b = (int) (1.164f * (y - 16) + 2.018f * (u - 128));
	
	                r = r < 0 ? 0 : (r > 255 ? 255 : r);
	                g = g < 0 ? 0 : (g > 255 ? 255 : g);
	                b = b < 0 ? 0 : (b > 255 ? 255 : b);
	
	                rgb[ty][tx][0] = r;
	                rgb[ty][tx][1] = g;
	                rgb[ty][tx][2] = b;
            	}
            }
        }
    }
    
    public void cavg(int[][][] rgb, int h, int w){
    	if(h==0 || w==0){
    		return;
    	}
    	int size = h*w;
    	for(int i=0;i<h;i++){
    		for(int j=0;j<w;j++){
    			if(rgb[i][j][0] == -1){
    				size--;
    			}
    			else{
	    			avg[0] += rgb[i][j][0];
	    			avg[1] += rgb[i][j][1];
	    			avg[2] += rgb[i][j][2];
    			}
    		}
    	}
    	avg[0] = avg[0]/(size);
    	avg[1] = avg[1]/(size);
    	avg[2] = avg[2]/(size);
    }
    
    public void cavg(int[][][] rgb){
    	int size = 2500;
    	for(int i=0;i<50;i++){
    		for(int j=0;j<50;j++){
    			if(rgb[i][j][0] == -1){
    				size--;
    			}
    			else{
	    			avg[0] += rgb[i][j][0];
	    			avg[1] += rgb[i][j][1];
	    			avg[2] += rgb[i][j][2];
    			}
    		}
    	}
    	avg[0] = avg[0]/(size);
    	avg[1] = avg[1]/(size);
    	avg[2] = avg[2]/(size);
    }
    
    private void draw_cavg(Canvas canvas){
    	
    	int temp;
    	temp = avg[0];
    	temp = temp << 8;
    	temp = temp | (0x000000ff & avg[1]);
    	temp = temp << 8;
    	temp = temp | (0x000000ff & avg[2]);
    	temp = temp | 0xff000000;
    	
    	paint.setARGB(255, 255, 255, 255);
    	
    	switch(f_mode){
    	case 0:
	    	canvas.drawText("0x"+Integer.toHexString(temp), 25, _h, paint);
	    	break;
    	case 1:
    		canvas.drawText("R:"+avg[0]+" G:"+avg[1]+" B:"+avg[2], 25, _h, paint);
    		break;
    	case 2:
    		canvas.drawText(guess(avg[0],avg[1],avg[2]), 25, _h, paint);
    		break;
    	}
    	
    	paint.setStyle(Paint.Style.FILL);
    	
    	paint.setARGB(255, avg[0], avg[1], avg[2]);
    	canvas.drawRect(0, 0, 25, _h, paint);
    }
    
    private String guess(int r, int g, int b){
    	if(((255*3)-r-g-b) <= 40){
    		return "White";
    	}
    	else if((r+g+b) <= 40){
    		return "Black";
    	}
    	else if(Math.abs(r-g-b) <= 25){
    		return "Grey";
    	}
    	else if(g+b <= r/2){
    		return "Red";
    	}
    	else if(r+g <= b/2){
    		return "Blue";
    	}
    	else if(r+b <= g/2){
    		return "Green";
    	}
    	else if(Math.abs(r-b) <= 25){
    		return "Magenta";
    	}
    	else if(Math.abs(r-g) <= 25){
    		return "Yellow";
    	}
    	else if(Math.abs(g-b) <= 25){
    		return "Cyan";
    	}
    	else{
    		return "???";
    	}
    }
    
}
